/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2006
* Sleepycat Software. All rights reserved.
*
* $Id: LatchImpl.java,v 1.1 2006/05/06 09:00:35 ckaestne Exp $
*/
package com.sleepycat.je.latch;
import java.util.ArrayList;
import java.util.List;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.RunRecoveryException;
import com.sleepycat.je.dbi.EnvironmentImpl;
/**
* This implementation is used in non-Java5 JVMs. In Java5 JVMs, the
* Java5LockWrapperImpl class is used. The switch hitting is performed in
* LatchSupport.
*
* Simple thread-based non-transactional exclusive non-nestable latch.
* <p>
* Latches provide simple exclusive transient locks on objects. Latches are
* expected to be held for short, defined periods of time. No deadlock
* detection is provided so it is the caller's responsibility to sequence latch
* acquisition in an ordered fashion to avoid deadlocks.
* <p>
* A latch can be acquire in wait or no-wait modes. In the former, the caller
* will wait for any conflicting holders to release the latch. In the latter,
* if the latch is not available, control returns to the caller immediately.
*/
public class LatchImpl implements Latch {
private static final String DEFAULT_LATCH_NAME = "LatchImpl";
private String name = null;
private List waiters = null;
private LatchStats stats = new LatchStats();
/* The object that the latch protects. */
private EnvironmentImpl env = null;
private Thread owner = null;
/**
* Create a latch.
*/
public LatchImpl(String name, EnvironmentImpl env) {
this.name = name;
this.env = env;
}
/**
* Create a latch with no name, more optimal for shortlived latches.
*/
public LatchImpl(EnvironmentImpl env) {
this.env = env;
this.name = DEFAULT_LATCH_NAME;
}
/**
* Set the latch name, used for latches in objects instantiated from
* the log.
*/
synchronized public void setName(String name) {
this.name = name;
}
/**
* Acquire a latch for exclusive/write access.
*
* <p>Wait for the latch if some other thread is holding it. If there are
* threads waiting for access, they will be granted the latch on a FIFO
* basis. When the method returns, the latch is held for exclusive
* access.</p>
*
* @throws LatchException if the latch is already held by the calling
* thread.
*
* @throws RunRecoveryException if an InterruptedException exception
* occurs.
*/
public void acquire()
throws DatabaseException {
try {
Thread thread = Thread.currentThread();
LatchWaiter waitTarget = null;
synchronized (this) {
if (thread == owner) {
stats.nAcquiresSelfOwned++;
throw new LatchException(getNameString() +
" already held");
}
if (owner == null) {
stats.nAcquiresNoWaiters++;
owner = thread;
} else {
if (waiters == null) {
waiters = new ArrayList();
}
waitTarget = new LatchWaiter(thread);
waiters.add(waitTarget);
stats.nAcquiresWithContention++;
}
}
if (waitTarget != null) {
synchronized (waitTarget) {
while (true) {
if (waitTarget.active) {
if (thread == owner) {
break;
} else {
throw new DatabaseException
("waitTarget.active but not owner");
}
} else {
waitTarget.wait();
if (thread == owner) {
break;
} else {
continue;
}
}
}
}
}
assert noteLatch(); // intentional side effect;
} catch (InterruptedException e) {
throw new RunRecoveryException(env, e);
} finally {
assert EnvironmentImpl.maybeForceYield();
}
}
/**
* Acquire a latch for exclusive/write access, but do not block if it's not
* available.
*
* @return true if the latch was acquired, false if it is not available.
*
* @throws LatchException if the latch is already held by the calling
* thread.
*/
public synchronized boolean acquireNoWait()
throws LatchException {
try {
Thread thread = Thread.currentThread();
if (thread == owner) {
stats.nAcquiresSelfOwned++;
throw new LatchException(getNameString() +
" already held");
}
if (owner == null) {
owner = thread;
stats.nAcquireNoWaitSuccessful++;
assert noteLatch(); // intentional side effect;
return true;
} else {
stats.nAcquireNoWaitUnsuccessful++;
return false;
}
} finally {
assert EnvironmentImpl.maybeForceYield();
}
}
/**
* Release the latch. If there are other thread(s) waiting for the latch,
* one is woken up and granted the latch. If the latch was not owned by
* the caller, just return;
*/
public void releaseIfOwner() {
doRelease(false);
}
/**
* Release the latch. If there are other thread(s) waiting for the latch,
* they are woken up and granted the latch.
*
* @throws LatchNotHeldException if the latch is not currently held.
*/
public void release()
throws LatchNotHeldException {
if (doRelease(true)) {
throw new LatchNotHeldException
(getNameString() + " not held");
}
}
/**
* Do the work of releasing the latch. Wake up any waiters.
*
* @returns true if this latch was not owned by the caller.
*/
private boolean doRelease(boolean checkHeld) {
LatchWaiter newOwner = null;
try {
synchronized (this) {
Thread thread = Thread.currentThread();
if (thread != owner) {
return true;
}
if (waiters != null && waiters.size() > 0) {
newOwner = (LatchWaiter) waiters.remove(0);
owner = (Thread) newOwner.thread;
} else {
owner = null;
}
stats.nReleases++;
assert unNoteLatch(checkHeld); // intentional side effect.
}
} finally {
assert EnvironmentImpl.maybeForceYield();
}
if (newOwner != null) {
synchronized (newOwner) {
newOwner.active = true;
newOwner.notifyAll();
}
}
return false;
}
/**
* Return true if the current thread holds this latch.
*
* @return true if we hold this latch. False otherwise.
*/
public boolean isOwner() {
return Thread.currentThread() == owner;
}
/**
* Used only for unit tests.
*
* @return the thread that currently holds the latch for exclusive access.
*/
public Thread owner() {
return owner;
}
/**
* Return the number of threads waiting.
*
* @return the number of threads waiting for the latch.
*/
public synchronized int nWaiters() {
return (waiters != null) ? waiters.size() : 0;
}
/**
* @return a LatchStats object with information about this latch.
*/
public LatchStats getLatchStats() {
LatchStats s = null;
try {
s = (LatchStats) stats.clone();
} catch (CloneNotSupportedException e) {
/* Klockwork - ok */
}
return s;
}
/**
* Formats a latch owner and waiters.
*/
public synchronized String toString() {
return LatchSupport.latchTable.toString(name, owner, waiters, 0);
}
/**
* For concocting exception messages
*/
private String getNameString() {
return LatchSupport.latchTable.getNameString(name);
}
/**
* Only call under the assert system. This records latching by thread.
*/
private boolean noteLatch()
throws LatchException {
return LatchSupport.latchTable.noteLatch(this);
}
/**
* Only call under the assert system. This records latching by thread.
*/
private boolean unNoteLatch(boolean checkHeld) {
/* Only return a false status if we are checking for latch ownership.*/
if (checkHeld) {
return LatchSupport.latchTable.unNoteLatch(this, name);
} else {
LatchSupport.latchTable.unNoteLatch(this, name);
return true;
}
}
/**
* Simple class that encapsulates a Thread to be 'notify()ed'.
*/
static private class LatchWaiter {
boolean active;
Thread thread;
LatchWaiter(Thread thread) {
this.thread = thread;
active = false;
}
public String toString() {
return "<LatchWaiter: " + thread + ">";
}
}
}